package com.cysoft.starter.log.core.aspect;

import com.cysoft.starter.log.core.annotation.IgnoreLog;
import com.cysoft.starter.log.core.helper.IpHelper;
import com.cysoft.starter.log.core.helper.LogHelper;
import com.cysoft.starter.log.core.helper.RequestParamsHelper;
import com.cysoft.starter.log.core.model.LogInfo;
import com.cysoft.starter.log.core.properties.LogIgnoreProperties;
import com.cysoft.starter.log.core.properties.OperateLogProperties;
import com.cysoft.starter.log.core.repository.DataRepository;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;


/**
 * @author 玖零
 * @version v0.0.1
 * @project cysoft-spring-boot-starter-log
 * @package com.cysoft.starter.log.core.aspect
 * @date 2025-02-04 12:06
 * @desc 日志切面基类,实现基础方法
 * 参考
 */
@Slf4j
public class BaseAspect {

    public static final String CLASS_METHOD_SPLIT = "#";

    /**
     * 数据仓库对象，用于存储和获取相关数据，通过构造函数注入，初始化后不可变
     */
    protected final DataRepository dataRepository;

    /**
     * 操作日志配置对象，包含日志记录的各种配置信息，通过构造函数注入，初始化后不可变
     */
    protected final OperateLogProperties operateLogProperties;

    /**
     * 声明静态的 ignorePackage 变量，用于存储需要忽略的包名集合。
     * 使用 volatile 关键字保证多线程环境下该变量的可见性，确保一个线程修改后其他线程能立即看到最新值。
     */
    protected static volatile Set<String> ignorePackage;

    /**
     * 声明静态的 ignoreClass 变量，用于存储需要忽略的类名集合。
     * 使用 volatile 关键字保证多线程环境下该变量的可见性，确保一个线程修改后其他线程能立即看到最新值。
     */
    protected static volatile Set<String> ignoreClass;

    /**
     * 声明静态的 ignoreMethods 变量，用于存储需要忽略的方法名集合。
     * 使用 volatile 关键字保证多线程环境下该变量的可见性，确保一个线程修改后其他线程能立即看到最新值。
     */
    protected static volatile Set<String> ignoreMethods;

    /**
     * 声明静态的 ignoreUrls 变量，用于存储需要忽略的 URL 地址集合。
     * 使用 volatile 关键字保证多线程环境下该变量的可见性，确保一个线程修改后其他线程能立即看到最新值。
     */
    protected static volatile Set<String> ignoreUrls;

    /**
     * 日志基础切面构造函数
     * @param dataRepository           数据仓库
     * @param operateLogProperties     日志配置
     */
    public BaseAspect(DataRepository dataRepository,OperateLogProperties operateLogProperties){
        this.dataRepository =  dataRepository;
        this.operateLogProperties = operateLogProperties;
        initLogIgnore(operateLogProperties.getIgnore());
    }

    /**
     * 初始化日志过滤信息
     * @param logIgnoreProperties {@link  LogIgnoreProperties}
     */
    private void initLogIgnore(LogIgnoreProperties logIgnoreProperties) {
        // 初始化需要忽略的包名集合
        LogHelper.initIgnore(ignorePackage, logIgnoreProperties.getIgnorePackages(), set -> ignorePackage = set);
        // 初始化需要忽略的类名集合
        LogHelper.initIgnore(ignoreClass, logIgnoreProperties.getIgnoreClasses(), set -> ignoreClass = set);
        // 初始化需要忽略的 URL 地址集合
        LogHelper.initIgnore(ignoreUrls, logIgnoreProperties.getIgnoreUrls(), set -> ignoreUrls = set);
        // 初始化需要忽略的方法名集合
        LogHelper.initIgnore(ignoreMethods, logIgnoreProperties.getIgnoreMethods(), set -> ignoreMethods = set);
        log.info("初始化结果 ignorePackage:{}  ------------ ignoreClass:{}  ------------ignoreUrls:{} ------------ ignoreMethods:{}",ignorePackage,ignoreClass,ignoreUrls,ignoreMethods);
    }

    /**
     *
     * @param request
     * @param point
     */
    protected Boolean checkIgnore(HttpServletRequest request, ProceedingJoinPoint point) {
        // 输入参数空值检查
        if (Objects.isNull(request) || Objects.isNull(point)) {
            log.warn("输入参数请求 或 ProceedingJoinPoint为空");
            return false;
        }
        //获取请求URI
        String requestURI = request.getRequestURI();
        //获取请求方法
        String method =  ((MethodSignature) point.getSignature()).getMethod().getName();
        //获取目标类的 Class 对象
        String fullPathClass = point.getSignature().getDeclaringTypeName();
        //获取目标类所在包
        String packageName = fullPathClass + CLASS_METHOD_SPLIT + point.getSignature().getName();
        // 检查方法或者类上是否存在 @IgnoreLog 注解
        boolean hasIgnoreLogAnnotation = hasIgnoreLogAnnotation(point);
        // 合并条件判断
        boolean shouldIgnore = ignoreUrls.contains(requestURI)
                || ignoreMethods.contains(method)
                || ignoreClass.contains(fullPathClass)
                || ignorePackage.contains(packageName)
                || hasIgnoreLogAnnotation;
        // 记录日志
        log.info("Request with URI {} and method {} is {}ignored.", requestURI, method, shouldIgnore ? "" : "not ");
        return shouldIgnore;
    }

    /**
     * 判断是否包含忽略注解
     * @param point
     * @return
     */
    private boolean hasIgnoreLogAnnotation(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 检查方法上是否存在 @IgnoreLog 注解
        if (method.isAnnotationPresent(IgnoreLog.class)) {
            return true;
        }
        // 检查类上是否存在 @IgnoreLog 注解
        Class<?> targetClass = point.getTarget().getClass();
        return targetClass.isAnnotationPresent(IgnoreLog.class);
    }

    /**
     * 拼接日志记录信息
     * @param request
     * @param point
     * @return
     */
    protected LogInfo assemblyRecord(HttpServletRequest request, ProceedingJoinPoint point) {
        LogInfo logInfo = new LogInfo();
        logInfo.setRequestUrl(request.getRequestURI());
        logInfo.setRequestIp(IpHelper.getIp(request));
        logInfo.setRequestMethod(request.getMethod());
        logInfo.setPackageInfo(RequestParamsHelper.getPackageInfo(point));
        logInfo.setRequestBody(RequestParamsHelper.getRequestParams(point));
        logInfo.setRequestHeader(RequestParamsHelper.getRequestHeader(request));
        logInfo.setClassInfo(RequestParamsHelper.getClassInfo(point));
        logInfo.setMethodInfo(RequestParamsHelper.getMethodInfo(point));
        //TODO:完善操作者获取方式
//        logInfo.setOperator();
        return  logInfo;
    }

    /**
     * 输出日志信息,根据配置文件判断使用哪种执行方式,按照策略的方式执行,服务在初始化的时候已经指定,因此此处
     * 只需要实现接口的定义即可
     * @param logInfo
     */
    protected void output(LogInfo logInfo) {
        dataRepository.output(logInfo);
    }
}
